/*
 * Università degli Studi di Trento
 * Facoltà di Scienze MM.FF.NN.
 * Corso di Laurea Triennale in Informatica
 * A.A. 2010/2011
 * Progetto per il corso di Sistemi Operativi 1
 * Progetto 4: simulazione di una rotonda
 * Autori:
 *  Abram Lorenzo - 140368
 *  Dal Lago Loris - 139428
 *  Florio Alessandro - 140245
 */


/********************************************************************************
  La Camera utilizza una politica di polling per controllare lo stato di Baut
********************************************************************************/

#include "camera.h"
#include "car.h"

typedef struct SCamera {
  bool prevEntries[4];
  bool prevRoad[Baut_LEN];
  bool prevExits[4];
  int nCarsInQuque[4];
} Camera;

/* struttura per incapsulare gli argomenti da passare alla funzione observe */
struct toObserve_data {
  ptrBaut baut;
  bool interactiveMode;
  bool * willQuitPtr;
  ptrCamera camera;
  pthread_mutex_t * camera_mutex;
};

static void * Camera_observe(void * arg); /* prototipo necessario */

static void Camera_init(ptrBaut baut, bool interactiveMode, bool * willQuitPtr, pthread_mutex_t * camera_mutex, ptrCamera camera){
  int i;
  for (i=0; i<Baut_LEN; i++){
    camera->prevRoad[i] = false;
  }
  for (i=0; i<4; i++){
    camera->prevEntries[i] = false;
    camera->prevExits[i] = false;
    camera->nCarsInQuque[i] = 0;
  }

  struct toObserve_data * arg = new(struct toObserve_data);  
  arg->baut = baut;
  arg->interactiveMode = interactiveMode;
  arg->willQuitPtr = willQuitPtr;
  arg->camera = camera;
  arg->camera_mutex = camera_mutex;

  pthread_t pt;
  int rc = pthread_create(&pt, NULL, Camera_observe, (void *) arg);
  if (rc) throw_error_ends_with_int("Camera_init", "return code from pthread_create() is", rc);
}

ptrCamera Camera_create(ptrBaut baut, bool interactiveMode, bool * willQuitPtr, pthread_mutex_t * camera_mutex){
  ptrCamera camera = new(Camera);
  Camera_init(baut, interactiveMode, willQuitPtr, camera_mutex, camera);
  return camera;
}

static void Camera_updateStatus(ptrCar car, ptrCamera camera){
	 if (car == (ptrCar)NULL)
	    throw_error("Camera_fprintf_log", "Macchina Nulla");

	  int event = Car_getEvent(car);

	  switch(event){
	  case ARRIVAL:
		  if (camera->nCarsInQuque[Car_getStartPoint(car)] == 0)
		  		  camera->prevEntries[Car_getStartPoint(car)] = true;
		  camera->nCarsInQuque[Car_getStartPoint(car)]++;
	    break;
	  case ENTER:
		  camera->nCarsInQuque[Car_getStartPoint(car)]--;
		  if (camera->nCarsInQuque[Car_getStartPoint(car)] == 0)
			  camera->prevEntries[Car_getStartPoint(car)] = false;
		  camera->prevRoad[Car_getStartPoint(car)*3] = true;
	    break;
	  case IN_SECTOR:
		  camera->prevRoad[Car_getPrevPos(car)] = false;
		  camera->prevRoad[Car_getPos(car)] = true;
	    break;
	  case EXIT:
		  camera->prevRoad[(Car_getDestPoint(car)*3+Baut_LEN-1)%Baut_LEN] = false;
		  camera->prevExits[Car_getDestPoint(car)] = true;
	    break;
	  case LEAVING:
		  camera->prevExits[Car_getDestPoint(car)] = false;
	    break;
	  default:
	    throw_error("Camera_update_status", "Evento non gestito!");
	  }
}

static void Camera_fprintf_log(FILE* log, ptrCar car, ptrCamera camera){
  if (car == (ptrCar)NULL)
    throw_error("Camera_fprintf_log", "Macchina Nulla");

  int event = Car_getEvent(car);
  time_t currentTime = time(NULL);
  struct tm * currentLocalTime = localtime(&currentTime);
  char buf[20];
  strftime(buf, 20, "%x %X", currentLocalTime);

  switch(event){
  case ARRIVAL:    
    fprintf(log, "%s AUTO %s ", buf, Car_getTag(car));
    fprintf(log, "ARRIVATA DA %s CON DESTINAZIONE %s",
	    Car_getStartPointStr(car),
	    Car_getDestPointStr(car));
    break;  
  case ENTER:
    fprintf(log, "%s AUTO %s ", buf, Car_getTag(car));
    fprintf(log, "HA OCCUPATO SETTORE %d", Car_getPos(car));
    break;
  case IN_SECTOR: 
    fprintf(log, "%s AUTO %s ", buf, Car_getTag(car));
    fprintf(log, "HA LIBERATO IL SETTORE %d DOPO %ld\n", Car_getPrevPos(car), Car_getSectStayTime(car));
    fprintf(log, "%s AUTO %s ", buf, Car_getTag(car));
    fprintf(log, "HA OCCUPATO SETTORE %d", Car_getPos(car));
    break;
  case EXIT: 
    fprintf(log, "%s AUTO %s ", buf, Car_getTag(car));
    fprintf(log, "HA LIBERATO IL SETTORE %d DOPO %ld\n", Car_getPos(car), Car_getSectStayTime(car));
    fprintf(log, "%s AUTO %s ", buf, Car_getTag(car));
    fprintf(log, "ARRIVATA A DESTINAZIONE %s", Car_getDestPointStr(car));
    break;
  case LEAVING:
    /* nothing to print */
    break;
  default:
    throw_error("Camera_fprintf_log", "Evento non gestito!");
  }

  if (event != LEAVING)
	  fprintf(log, "\n");
}

static void printLetter(bool car, FILE* file){
  fprintf(file, car ? "A" : "X");
}

static void Camera_fprintf_scenario(FILE* file, ptrCamera camera){
  fprintf(file, "  ");
  printLetter(camera->prevEntries[2], file);  /* entrata Povo */
  printLetter(camera->prevExits[2], file);   /* uscita Povo  */
  fprintf(file, "\n");

  fprintf(file, " ");
  printLetter(camera->prevRoad[7], file);  /* settore 1 */
  printLetter(camera->prevRoad[6], file);  /* settore 2 */
  printLetter(camera->prevRoad[5], file);  /* settore 3 */
  printLetter(camera->prevRoad[4], file);  /* settore 4 */
  fprintf(file, "\n");  

  printLetter(camera->prevExits[3], file);   /* uscita IRST */
  printLetter(camera->prevRoad[8], file); /* settore C   */
  fprintf(file, "  ");
  printLetter(camera->prevRoad[3], file);  /* settore 5          */
  printLetter(camera->prevEntries[1], file);  /* entrata Villazzano */
  fprintf(file, "\n");  

  printLetter(camera->prevEntries[3], file);  /* entrata IRST */
  printLetter(camera->prevRoad[9], file); /* settore B    */
  fprintf(file, "  ");
  printLetter(camera->prevRoad[2], file);  /* settore 6         */
  printLetter(camera->prevExits[1], file);   /* uscita Villazzano */
  fprintf(file, "\n");  

  fprintf(file, " ");
  printLetter(camera->prevRoad[10], file);  /* settore A */
  printLetter(camera->prevRoad[11], file);  /* settore 9 */
  printLetter(camera->prevRoad[0], file);  /* settore 8 */
  printLetter(camera->prevRoad[1], file);  /* settore 7 */
  fprintf(file, "\n");  

  fprintf(file, "  ");
  printLetter(camera->prevExits[0], file);   /* uscita Mesiano  */
  printLetter(camera->prevEntries[0], file);  /* entrata Mesiano */
  fprintf(file, "\n\n");
}


/* Thread che gestisce il comportamento della telecamera */
static void * Camera_observe(void * arg){

  struct toObserve_data * data = (struct toObserve_data *) arg;
  FILE * logFile = fopen(logFile_name, "w");
  FILE * streamingFile = (data->interactiveMode) ? fopen(streamingFile_name, "w") : stdout;
#ifdef DEBUG_CAMERA
  streamingFile = stdout;
#endif

  while(!*data->willQuitPtr){
    ptrCar car;
        if (read(Baut_getReadFifo(data->baut), &car, sizeof(car)) > 0){
			if(car == (ptrCar)NULL){
				throw_error("Camera_observe", "Car nulla!");
			}
			else {
				Camera_updateStatus(car, data->camera);
				Camera_fprintf_log(logFile, car, data->camera);
				Camera_fprintf_scenario(streamingFile, data->camera);
				Car_destroy(car);
			}
        }
  }

  bool fileCloseError;
  fileCloseError = fclose(logFile);
  if (fileCloseError) throw_error_ends_with_str("Camera_observe", "errore nella chiusura del file", logFile_name);

  if (data->interactiveMode) {
    fileCloseError = fclose(streamingFile);
    if (fileCloseError) throw_error_ends_with_str("Camera_observe", "errore nella chiusura del file", streamingFile_name);
  }
  pthread_mutex_unlock(data->camera_mutex);
  pthread_exit(NULL);
}


#ifdef DEBUG
void Camera_printf (ptrCamera camera){
  /* BLA, BLA, BLA */
}
#endif /* DEBUG */

static void Camera_deinit(ptrCamera camera){
  /* no pointers to deinit */
}

void Camera_destroy(ptrCamera camera){
  if (camera != NULL){
    Camera_deinit(camera);
    delete(camera);
  }
}
